package data.analysis.grammar.symbol;

import stream.DebugStream;
import stream.ErrorStream;
import data.analysis.grammar.LocStatAST;
import data.analysis.grammar.LocStatLexer;

public class SymbolTable {
	public static final Symbol UNRESOLVED_SYMBOL = new Symbol("Unresolved", NativeType.VOID, null);
	public static final Scope NO_SCOPE = new BaseScope("No Scope");
	
	private final Scope globalScope;
	private final DebugStream db;
	private final ErrorStream err;
	
	public SymbolTable(Scope globalScope, DebugStream db, ErrorStream err) {
		this.globalScope = globalScope;
		this.db = db;
		this.err = err;
		
		populateTypeSymbols();
	}
	
	public Scope getGlobalScope() {
		return globalScope;
	}
	
	public Symbol resolve(LocStatAST name) {
		Symbol s = name.scope.resolve(name.getText());
		if (s == null) {
			s = UNRESOLVED_SYMBOL;
			err.write("Line " + name.getLine() + ": symbol '" + name.getText() + "' could not be resolved.");
		}
		name.symbol = s;
		name.evalType = s.getType();
		((LocStatAST)name.getParent()).evalType = s.getType();
		return s;
	}
	
	public Symbol resolveScript(LocStatAST script) {
		Symbol s = script.scope.resolve("script");
		if (s == null) {
			s = UNRESOLVED_SYMBOL;
			err.write("Line " + script.getLine() + ": scripts cannot be invoked on type '" + script.scope.getName() + "'.");
		}
		script.symbol = s;
		script.evalType = s.getType();
		return s;
	}
	
	public Scope findCondScope(LocStatAST cond) {
		LocStatAST parent = (LocStatAST)cond.getParent();
		if (parent.getType() == LocStatLexer.SUBJECT) {
			LocStatAST subjName = (LocStatAST)parent.getChild(0);
			Symbol subjSymbol = subjName.symbol;
			if (subjSymbol instanceof Scope) {
				return (Scope)subjSymbol;
			} else {
				err.write("Line " + cond.getLine() + ": '" + subjName.getText() + "' cannot be conditionalized.");
				return NO_SCOPE;
			}
		} else {
			return parent.scope;
		}
	}
	
	public Scope findScopeOpScope(LocStatAST scopeOp) {
		LocStatAST subjName = (LocStatAST)scopeOp.getParent().getChild(0).getChild(0);
		Symbol subjSymbol = subjName.symbol;
		if (subjSymbol instanceof Scope) {
			return (Scope)subjSymbol;
		} else {
			err.write("Line " + scopeOp.getLine() + ": '" + subjName.getText() + "' has no member attributes.");
			return NO_SCOPE;
		}
	}
	
	public void handleCount(LocStatAST subj) {
		if (!subj.evalType.equals(NativeType.INT)) {
			LocStatAST name = (LocStatAST)subj.getChild(0);
			err.write("Line " + name.getLine() + ": '" + name.getText() + "' is not a countable attribute.");
		}
	}
	
	public void handleFrac(LocStatAST num, LocStatAST denom) {
		if (!num.evalType.equals(NativeType.INT)) {
			LocStatAST name = (LocStatAST)num.getChild(0);
			err.write("Line " + name.getLine() + ": '" + name.getText() + "' is not a countable attribute.");
		}
		if (!denom.evalType.equals(NativeType.INT)) {
			LocStatAST name = (LocStatAST)denom.getChild(0);
			err.write("Line " + name.getLine() + ": '" + name.getText() + "' is not a countable attribute.");
		}
	}
	
	public void handleMean(LocStatAST subj, LocStatAST scopeOp) {
		if (!subj.evalType.equals(NativeType.INT)) {
			LocStatAST name = (LocStatAST)subj.getChild(0);
			err.write("Line " + name.getLine() + ": '" + name.getText() + "' is not a countable attribute.");
		}
		if (!scopeOp.evalType.equals(NativeType.INT)) {
			LocStatAST scopedSubj = (LocStatAST)scopeOp.getChild(0).getChild(0);
			err.write("Line " + scopedSubj.getLine() + ": '" + scopedSubj.getText() + "' is not a countable attribute.");
		}
	}
	
	public void handleMed(LocStatAST subj, LocStatAST scopeOp) {
		if (!subj.evalType.equals(NativeType.INT)) {
			LocStatAST name = (LocStatAST)subj.getChild(0);
			err.write("Line " + name.getLine() + ": '" + name.getText() + "' is not a countable attribute.");
		}
		if (!scopeOp.evalType.equals(NativeType.INT)) {
			LocStatAST scopedSubj = (LocStatAST)scopeOp.getChild(0).getChild(0);
			err.write("Line " + scopedSubj.getLine() + ": '" + scopedSubj.getText() + "' is not a countable attribute.");
		}
	}
	
	public void handleCond(LocStatAST root) {
		LocStatAST op = (LocStatAST)root.getChild(0);
		LocStatAST cond = (LocStatAST)root.getChild(1);
		Type type;
		switch (op.getType()) {
		case LocStatLexer.COND_DEFAULT:
		case LocStatLexer.NOT:
		case LocStatLexer.SCRIPT:
			if (cond.evalType.equals(NativeType.BOOLEAN)) {
				type = NativeType.BOOLEAN;
			} else {
				type = NativeType.VOID;
				err.write("Line " + root.getLine() + ": '" + root.getText() + "' is not a boolean attribute.");
			}
			break;
		case LocStatLexer.EXACTLY_ONE:
		case LocStatLexer.ONE_OR_MORE:
			if (cond.evalType.equals(NativeType.INT)) {
				type = NativeType.BOOLEAN;
			} else {
				type = NativeType.VOID;
				err.write("Line " + root.getLine() + ": '" + root.getText() + "' is not a countable attribute.");
			}
			break;
		default:
			type = NativeType.VOID;
		}
		root.evalType = type;
	}
	
	private void populateTypeSymbols() {
		globalScope.define(NativeType.INT.getTypeSymbol());
		globalScope.define(NativeType.DOUBLE.getTypeSymbol());
		globalScope.define(NativeType.BOOLEAN.getTypeSymbol());
	}
}
